package jp.wug;

import com.google.inject.*;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletModule;
import jp.wug.core.*;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import java.util.Set;

public class WugServlet extends HttpServlet {
    private Injector injector;

    private static class WugModule extends AbstractModule {
        private final PageFactory pageFactory;
        private final ConversationScope conversationScope;

        WugModule(PageFactory pageFactory, ConversationScope conversationScope) {
            this.pageFactory = pageFactory;
            this.conversationScope = conversationScope;
        }

        @Override
        public void configure() {
            bind(PageFactory.class).toInstance(pageFactory);
            bindScope(ConversationScoped.class, conversationScope);
        }

        @Provides
        @RequestScoped
        Locale provideLocale(WugSession session) {
            return session.getLocale();
        }

//        @Provides
//        ResourceBundle provideResourceBundle(WugRequest wugRequest) {
//            return wugRequest.getResourceBundle();
//        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        PageFactory pageFactory = new PageFactory();
        KeyedObjectModule keyedObjectModule = new KeyedObjectModule();
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            // factory.setIgnoringElementContentWhitespace(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            findTemplatePaths(pageFactory, keyedObjectModule, builder, "/");
        } catch (MalformedURLException e) {
            throw new ServletException(e);
        } catch (ParserConfigurationException e) {
            throw new ServletException(e);
        } catch (SAXException e) {
            throw new ServletException(e);
        } catch (IOException e) {
            throw new ServletException(e);
        } catch (ClassNotFoundException e) {
            throw new ServletException(e);
        }

        ConversationScope conversationScope = new ConversationScope();
        Module wugModule = new WugModule(pageFactory, conversationScope);
        injector = Guice.createInjector(new ServletModule(), wugModule, keyedObjectModule);

        pageFactory.init(injector);
        conversationScope.setRequestProvider(injector.getProvider(WugRequest.class));
    }

    private void findTemplatePaths(PageFactory pageFactory, KeyedObjectModule keyedObjectModule, DocumentBuilder builder, String pathInfo) throws SAXException, IOException, ClassNotFoundException {
        ServletContext servletContext = getServletContext();
        Set<?> paths = servletContext.getResourcePaths("/WEB-INF/template" + pathInfo);
        if (paths == null) {
            return;
        }
        for (Object pathObject : paths) {
            String path = (String) pathObject;
            String tail = path.substring(path.lastIndexOf('/', path.length() - 2) + 1);
            if (tail.endsWith("/")) {
                findTemplatePaths(pageFactory, keyedObjectModule, builder, pathInfo + tail);
                continue;
            }
            URL url = servletContext.getResource(path);
            Document document = builder.parse(url.toString());
            preprocess(document);
            if (tail.equals("index.html")) {
                pageFactory.register(pathInfo, document, keyedObjectModule);
                // pageFactory.register(new String[] {pathInfo,
                // pathInfo.substring(0, pathInfo.length() - 1), pathInfo +
                // tail}, document);
            } else {
                pageFactory.register(pathInfo + tail, document, keyedObjectModule);
            }
        }
    }

    private void preprocess(Document document) {
        Preprocessor preprocessor = new Preprocessor();
        preprocessor.preprocess(document);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        process(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        process(request, response);
    }

    private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PageFactory pageFactory = injector.getInstance(PageFactory.class);
        String pathInfo = request.getPathInfo();
        Class<?> modelType = pageFactory.getTypeByPath(pathInfo);

        if (modelType == null) {
            RequestDispatcher fallbackDispathcer = getServletContext().getNamedDispatcher("default");
            fallbackDispathcer.forward(request, response);
            return;
        }

        WugRequest wugRequest = injector.getInstance(WugRequest.class);
        wugRequest.prepare(modelType);

        Object object = injector.getInstance(modelType);
        Page page = pageFactory.create(modelType, object);

        List<ModelException> exceptions = page.fill(wugRequest);
        if (exceptions.size() > 0) {
            throw new ServletException(exceptions.get(0));
        }

        if (request.getMethod().equals("POST")) {
            page.post(wugRequest);
        }

        String[] action = wugRequest.getQueryParameter("_action");
        if (action.length == 1) {
            page.action(action[0], wugRequest);
        }

        PageFlow pageFlow = injector.getInstance(PageFlow.class);
        if (!pageFlow.isHandled()) {
            String[] as = wugRequest.getQueryParameter("_as");
            if (as.length != 1) {
                response.setContentType("application/xhtml+xml");
                response.setCharacterEncoding("UTF-8");
                Writer writer = response.getWriter();
                writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                page.write(writer);
            } else if (as[0].equals("json")) {
                response.setContentType("application/json");
                response.setCharacterEncoding("UTF-8");

                Writer writer = response.getWriter();
                page.dump(writer);
            } else if (as[0].equals("debug-information")) {
                response.setContentType("text/plain");
                response.setCharacterEncoding("UTF-8");

                PrintWriter writer = response.getWriter();
                // page.writeDebugInformation(writer);
                writer.println("--------Model");
                page.dump(writer);
            }
        }
    }
}